#include "stdafx.h"
#include "base/thread/MessagePumpForUI.h"

namespace xBase
{
    const wchar_t kWndClass[] = L"xBase_MessagePumpWindow";

    const INT kMsgHaveWork          = 0x6001;
    const INT kMessageFilterCode    = 0x5001;

    CxMessagePumpForUI::CxMessagePumpForUI()
    {

    }

    CxMessagePumpForUI::~CxMessagePumpForUI()
    {
        ::DestroyWindow(m_hMsgWnd);
        ::UnregisterClass(kWndClass, ::GetModuleHandle(NULL));
    }

    VOID CxMessagePumpForUI::ScheduleWork()
    {
        if (::InterlockedExchange(&m_nHaveWork, 1))
            return;

        ::PostMessage(m_hMsgWnd, kMsgHaveWork, reinterpret_cast<WPARAM>(this), 0);
    }

    VOID CxMessagePumpForUI::ScheduleDelayedWork(const xBase::CTime& oDelayedWorkTime)
    {
        m_oDelayedWorkTime = oDelayedWorkTime;

        INT nDelayMilliSec = GetCurrentDelay();

        if (nDelayMilliSec < USER_TIMER_MINIMUM)
            nDelayMilliSec = USER_TIMER_MINIMUM;

        ::SetTimer(m_hMsgWnd, reinterpret_cast<UINT_PTR>(this), static_cast<UINT>(nDelayMilliSec), NULL);
    }

    VOID CxMessagePumpForUI::PumpOutPendingPaintMessages()
    {
        if (!m_pState)
            return;

        const INT kMaxPeekCount = 20;

        for (INT nPeekCount = 0; nPeekCount < kMaxPeekCount; ++nPeekCount)
        {
            MSG msg = {0};
            if (!::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_QS_PAINT))
                break;

            ProcessMessageHelper(msg);

            if (m_pState->m_bShouldQuit)
                break;
        }
    }

    BOOL CxMessagePumpForUI::AddMsgFilter(CxMessageFilter* pFilter)
    {
        std::list<CxMessageFilter*>::iterator it = std::find(m_ltMsgFilter.begin(), m_ltMsgFilter.end(), pFilter);
        if (it != m_ltMsgFilter.end())
        {
            return FALSE;
        }
        else
        {
            m_ltMsgFilter.push_back(pFilter);
            return TRUE;
        }
    }

    BOOL CxMessagePumpForUI::RemoveMsgFilter(CxMessageFilter* pFilter)
    {
        std::list<CxMessageFilter*>::iterator it = std::find(m_ltMsgFilter.begin(), m_ltMsgFilter.end(), pFilter);
        if (it != m_ltMsgFilter.end())
        {
            m_ltMsgFilter.erase(it);
            return TRUE;
        }
        else
        {
            return FALSE;
        }
    }

    BOOL CxMessagePumpForUI::ProcessMessage(const MSG& msg)
    {
        if (!m_ltMsgFilter.empty())
        {
            std::list<CxMessageFilter*>::reverse_iterator rit = m_ltMsgFilter.rbegin();

            while (rit != m_ltMsgFilter.rend())
            {
                CxMessageFilter* pFilter = *rit;
                if (pFilter && pFilter->PreTranslateMessage(const_cast<MSG*>(&msg)))
                {
                    return TRUE;
                }
            }
        }

        return FALSE;
    }


    //Private////////////////////////////////////////////////////////////////////////
    LRESULT CALLBACK CxMessagePumpForUI::WndProcThunk(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        switch (message)
        {
        case kMsgHaveWork:
            {
                wchar_t wszFlagName[MAX_PATH] = {0};
                swprintf_s(wszFlagName, MAX_PATH, L"FLAG_DISABLE_PUMP-%d", ::GetCurrentProcessId());
                ATOM hAtom = ::FindAtom(wszFlagName);
                if (hAtom)
                {
                    return 0;
                }
                reinterpret_cast<CxMessagePumpForUI*>(wParam)->HandleWorkMessage();
            }
            break;
        case WM_TIMER:
            reinterpret_cast<CxMessagePumpForUI*>(wParam)->HandleTimerMessage();
            break;
        }

        return ::DefWindowProc(hWnd, message, wParam, lParam);
    }

    VOID CxMessagePumpForUI::DoRunLoop()
    {
        for (;;)
        {
            BOOL bMoreWorkIsPlausible = ProcessNextWindowsMessage();
            if (m_pState->m_bShouldQuit)
                break;

            bMoreWorkIsPlausible |= m_pState->m_pDelegate->DoWork();
            if (m_pState->m_bShouldQuit)
                break;

            bMoreWorkIsPlausible |= m_pState->m_pDelegate->DoDelayedWork(&m_oDelayedWorkTime);

            if (bMoreWorkIsPlausible && m_oDelayedWorkTime.IsNull())
            {
                ::KillTimer(m_hMsgWnd, reinterpret_cast<UINT_PTR>(this));
            }

            if (m_pState->m_bShouldQuit)
                break;

            if (bMoreWorkIsPlausible)
                continue;

            WaitForWork();
        }
    }

    VOID CxMessagePumpForUI::InitMessageWnd()
    {
        HINSTANCE hInstance = ::GetModuleHandle(NULL);

        WNDCLASSEX wc = {0};
        wc.cbSize = sizeof(wc);
        wc.lpfnWndProc = WndProcThunk;
        wc.hInstance = hInstance;
        wc.lpszClassName = kWndClass;

        ::RegisterClassEx(&wc);

        m_hMsgWnd = ::CreateWindow(kWndClass, 0, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, hInstance, 0);
    }

    VOID CxMessagePumpForUI::WaitForWork()
    {
        INT nDelay = GetCurrentDelay();

        if (nDelay < 0)
            nDelay = INFINITE;

        DWORD dwResult = ::MsgWaitForMultipleObjectsEx(
            0,
            NULL,
            static_cast<DWORD>(nDelay),
            QS_ALLINPUT,
            MWMO_INPUTAVAILABLE);

        if (WAIT_OBJECT_0 == dwResult)
        {
            // A WM_* message is available.
            // If a parent child relationship exists between windows across threads
            // then their thread inputs are implicitly attached.
            // This causes the MsgWaitForMultipleObjectsEx API to return indicating
            // that messages are ready for processing (specifically mouse messages
            // intended for the child window. Occurs if the child window has capture)
            // The subsequent PeekMessages call fails to return any messages thus
            // causing us to enter a tight loop at times.
            // The WaitMessage call below is a workaround to give the child window
            // sometime to process its input messages.

            MSG msg = {0};
            DWORD dwQueueStatus = ::GetQueueStatus(QS_MOUSE);
            if (HIWORD(dwQueueStatus) & QS_MOUSE &&
                !::PeekMessage(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_NOREMOVE))
            {
                ::WaitMessage();
            }

            return;
        }
    }

    VOID CxMessagePumpForUI::HandleWorkMessage()
    {
        if (!m_pState)
        {
            ::InterlockedExchange(&m_nHaveWork, 0);
            return;
        }

        ProcessPumpReplacementMessage();

        if (m_pState->m_pDelegate->DoWork())
            ScheduleWork();
    }

    VOID CxMessagePumpForUI::HandleTimerMessage()
    {
        ::KillTimer(m_hMsgWnd, reinterpret_cast<UINT_PTR>(this));

        if (!m_pState)
            return;

        m_pState->m_pDelegate->DoDelayedWork(&m_oDelayedWorkTime);

        if (!m_oDelayedWorkTime.IsNull())
        {
            ScheduleDelayedWork(m_oDelayedWorkTime);
        }
    }

    BOOL CxMessagePumpForUI::ProcessNextWindowsMessage()
    {
        BOOL bSentMessagesInQueue = FALSE;

        DWORD dwQueueStatus = ::GetQueueStatus(QS_SENDMESSAGE);

        if (HIWORD(dwQueueStatus) & QS_SENDMESSAGE)
            bSentMessagesInQueue = TRUE;

        MSG msg = {0};
        if (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
            return ProcessMessageHelper(msg);

        return bSentMessagesInQueue;
    }

    BOOL CxMessagePumpForUI::ProcessMessageHelper(const MSG& msg)
    {
        if (WM_QUIT == msg.message) 
        {
            m_pState->m_bShouldQuit = TRUE;
            ::PostQuitMessage(static_cast<int>(msg.wParam));
            return FALSE;
        }

        if (msg.message == kMsgHaveWork && msg.hwnd == m_hMsgWnd)
            return ProcessPumpReplacementMessage();

        if (::CallMsgFilter(const_cast<MSG*>(&msg), kMessageFilterCode))
            return TRUE;

        if (!PreTranslateMessage(&msg))
        {
            ::TranslateMessage(&msg);
            ::DispatchMessage(&msg);
        }

        return TRUE;
    }

    BOOL CxMessagePumpForUI::ProcessPumpReplacementMessage()
    {
        MSG msg = {0};
        BOOL bHaveMessage = (0 != ::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE));

        ::InterlockedExchange(&m_nHaveWork, 0);

        if (!bHaveMessage)
            return FALSE;

        ScheduleWork();

        return ProcessMessageHelper(msg);
    }

    BOOL CxMessagePumpForUI::PreTranslateMessage(const MSG* pMsg)
    {
        MSG* pNonConstMsg = const_cast<MSG*>(pMsg);

        if (!m_ltMsgFilter.empty())
        {
            std::list<CxMessageFilter*>::reverse_iterator rit = m_ltMsgFilter.rbegin();
            while (rit != m_ltMsgFilter.rend())
            {
                CxMessageFilter* pFilter = *rit;
                if (pFilter && pFilter->PreTranslateMessage(pNonConstMsg))
                {
                    return TRUE;
                }
            }
        }

        return FALSE;
    }
}